﻿using System;
using System.Collections.Generic;
using System.Text;
using AgilePlannerDT.Data;
using AgilePlannerDT.Display;
using AgilePlannerDT.Application.Commands.Impl;
using System.Windows;
using AgilePlannerDT.Utils;
using AgilePlannerDT.Persister;
using System.Windows.Threading;
using AgilePlannerDT.Display.Events;
using AgilePlannerDT.Data.Impl;
using AgilePlannerDT.Display.Impl;
using AgilePlannerDT.Persister.Impl.Socket;

namespace AgilePlannerDT.Application.Modules.Impl
{
    public class PersisterModuleImpl : AbstractApplicationModule, PersisterModule
    {

        #region Attributes

        protected AsynchronousPersister persister;

        #endregion

        #region change UI delegation
        #endregion
        #region Constructors

        public PersisterModuleImpl(AgilePlannerApplication app, Dispatcher dispatcher, AsynchronousPersister persister)
        {
            this.app = app;
            this.dispatcher = dispatcher;
            this.persister = persister;

        }

        #endregion

        #region Properties

        public AsynchronousPersister Persister
        {
            get { return this.persister; }
            set { this.persister = value; }
        }

        public Project Project
        {
            get { return persister.Project; }
            set
            {
                persister.Project = value;
            }
        }

        #endregion

        #region PlannerDataChangeListener Members

        public void OnLoadedProject(Project project)
        {
            //clear all the commands 
            Application.Commands.Clear();

            Application.IsLoadingPhase = true;

            if (Application.Project != null)
            {
                Application.Clear();
                
            }
           Application.Project = project;
           

            foreach (Iteration iter in Application.Project.IterationChildren)
            {
                IterationDisplayObject iteration = Application.CreateIteration(iter);

                foreach (StoryCard sc in iter.StoryCardChildren)
                {
                    StoryCardDisplayObject storycard;
                    if(this.persister is AgilePlannerDT.Persister.Impl.AsyncWrapperPersister)
                        storycard = Application.CreateStoryCard((StoryCard)sc.Clone());
                    else
                        storycard = Application.CreateStoryCard(sc);
                    iteration.AddStoryCard(storycard);
                }
            }

            foreach (StoryCard sc in Application.Project.Backlog.StoryCardChildren)
            {
                StoryCardDisplayObject storycard;
                if (this.persister is AgilePlannerDT.Persister.Impl.AsyncWrapperPersister)
                    storycard = Application.CreateStoryCard((StoryCard)sc.Clone());
                else
                    storycard = Application.CreateStoryCard(sc);
            }

            Application.IsLoadingPhase = false;
        }

        public void OnCreatedIteration(Iteration iteration, bool isCreatedByRemoteClient)
        {
            IterationDisplayObject iter = Application.CreateIteration(iteration);

            if(!isCreatedByRemoteClient)
            CollectStoryCards(iteration);

            Application.Commands.Add(new IterationCreateCommand(Application, (Iteration)iteration.Clone()));
        }

        public void OnCreatedStoryCard(StoryCard storycard)
        {
            StoryCardDisplayObject sc = Application.CreateStoryCard(storycard);
         
            //if the create operation is called by other client, the card must be added to the main card list in Main Applicaiton
            if (!Application.Cards.ContainsKey(sc.ID))
                Application.Cards.Add(sc.ID, sc);
            if (Application.Project == null)
            {
                MessageBox.Show("Cannot create story card. Please to a project first!", "Error");
            }
            else if (storycard.Parent != Application.Project.Backlog.ID)
            {
               
                //add to display layer and dataobject layer
                DisplayObject parent;

                if (Application.Cards.TryGetValue(storycard.Parent, out parent))
                {
                    ((IterationDisplayObject)parent).AddStoryCard(sc);
                    if (!(((IterationDisplayObject)parent).IterationData.StoryCardChildren.Contains(sc.StoryCardData)))
                    ((IterationDisplayObject)parent).IterationData.AddStoryCard(sc.StoryCardData);
                }

            }

            Application.Commands.Add(new StoryCardCreateCommand(Application, (StoryCard)storycard.Clone()));
        }

        public void OnUpdatedStoryCard(StoryCard storycard)
        {
            DisplayObject dobj1, dobj2;

            if (Application.Cards.TryGetValue(storycard.ID, out dobj2))
            {
                StoryCardDisplayObject sc = (StoryCardDisplayObject)dobj2;

                if (storycard.Equals(sc.StoryCardData))
                {
                    //do nothing, this is a change of one of the text
                    //fields that was triggered on this client
                }

                if (storycard.Height == CardConstants.DEFAULT_MINIMIZED_CARD_HEIGHT && sc.StoryCardData.Height != CardConstants.DEFAULT_MINIMIZED_CARD_HEIGHT)
                {
                    sc.StoryCardData.Height = storycard.Height;
                    ((StoryCardDisplayObjectImpl)sc).CardMinimized(false);
                    return;
                }
                if (storycard.Height != CardConstants.DEFAULT_MINIMIZED_CARD_HEIGHT && sc.StoryCardData.Height == CardConstants.DEFAULT_MINIMIZED_CARD_HEIGHT)
                {
                    sc.StoryCardData.Height = storycard.Height;
                    ((StoryCardDisplayObjectImpl)sc).CardMinimized(true);
                    return;
                }
                if ((int)storycard.Width != (int)(sc.StoryCardData.Width) || (int)storycard.Height != (int)(sc.StoryCardData.Height))
                {
                    //resize triggered by another client
                    StoryCard before = (StoryCard)sc.StoryCardData.Clone();
                    StoryCard after = (StoryCard)sc.StoryCardData.Clone();
                    after.Width = storycard.Width;
                    after.Height = storycard.Height;
     //               Application.Commands.Add(new StoryCardUpdateCommand(Application, before, after));

                    double dx = storycard.Width - sc.Width;
                    double dy = storycard.Height - sc.Height;
                    //change the interface layer

                    sc.Resize(ref dx, ref dy);
                  //  ((StoryCardDisplayObjectImpl)sc).setRotationCenter(sc.Width / 2, sc.Height / 2);
                   ((DisplayObjectImpl)sc).refitCardCenterAndAbsolutePosition(dx, dy);
                    //change the dataobject layer
                    sc.StoryCardData.Width = storycard.Width;
                    sc.StoryCardData.Height = storycard.Height;
                }
                if (Math.Abs((int)(storycard.LocationX*Properties.Settings.Default.Scale) - (int)sc.LocationX)> 2||
                    Math.Abs((int)(storycard.LocationY*Properties.Settings.Default.Scale) - (int)sc.LocationY)>2 ||
                    Math.Abs((int)storycard.RotationAngle - (int)sc.RotationAngle) >2||
                    storycard.Parent != sc.ParentID)
                {
                    //this is a movement triggered by another client,
                    //possibly a MoveToNewParent
               //     ((StoryCardDisplayObjectImpl)sc).an
               //     Console.WriteLine("Updated Story Card "+sc.ID+" to false");
                    ((StoryCardDisplayObjectImpl)sc).setTriggerDropEventAfterAnimiationComplete(false);
                    sc.AnimatedMoveTo(storycard.LocationX*Properties.Settings.Default.Scale, storycard.LocationY*Properties.Settings.Default.Scale, storycard.RotationAngle);
              //      ((StoryCardDisplayObjectImpl)sc).setTriggerDropEventAfterAnimiationComplete(true);
                }
                if ((int)storycard.LocationX != (int)sc.StoryCardData.LocationX ||
                    (int)storycard.LocationY != (int)sc.StoryCardData.LocationY ||
                    (int)storycard.RotationAngle != (int)sc.StoryCardData.RotationAngle)
                {
                    //we checked that the display object is at the right place, now we update the underlying
                    //data object to match the current position
                    sc.StoryCardData.Update(storycard);

  //                  Application.Commands.Add(new StoryCardUpdateCommand(Application, (StoryCard)before, (StoryCard)after));
                }

                else
                {
                    //a change of one of the text fields triggered by any of the other
                    //clients. need to check them one by one in order to trigger the update to
                    //the UI
                    //right now a simple update is enough

                    sc.StoryCardData.Update(storycard);
                    ((StoryCardDisplayObjectImpl)dobj2).refreshCardTextBoxes();
                    //make sure the color of card has been updated
                    sc.CardColor = storycard.Background;
                }
            }

            if (Application.Cards.TryGetValue(storycard.Parent, out dobj1))
                ((IterationDisplayObject)dobj1).UpdateRemainingEffort();
        }

        public void OnUpdatedLegend(Legend legend)
        {

        }

        public void OnUpdateTeamMember(List<TeamMember> members)
        {
        }

        public void OnAsynchronousException(Exception exception)
        {
            MessageBox.Show(exception.Message, "Error occured in Persister: " + exception.GetType().ToString());
        }

        public void OnDeletedStoryCard(StoryCard storycard)
        {
            DisplayObject card;
            Application.Cards.TryGetValue(storycard.ID, out card);
            if (card is StoryCardDisplayObject)
            {
                DisplayObject parent;
                if (Application.Cards.TryGetValue(storycard.Parent, out parent))
                {
                    if (parent is IterationDisplayObject)
                    {
                        ((IterationDisplayObject)parent).RemoveStoryCard((StoryCardDisplayObject)card);
                        ((IterationDisplayObject)parent).IterationData.RemoveStoryCard(((StoryCardDisplayObject)card).StoryCardData);
                    }
//                    if (parent is BacklogDisplayObject)
//                    {
//                        ((BacklogDisplayObject)parent).BacklogData.RemoveStoryCard(storycard);
//                    }
                }
                Application.Cards.Remove(card.ID);
                card.FadeOut();
            }
        }

        public void OnDeletedIteration(Iteration iteration)
        {
            DisplayObject card;
            Application.Cards.TryGetValue(iteration.ID, out card);

            Application.Cards.Remove(iteration.ID);

            if (card is IterationDisplayObject)
            {
  //              DisplayObject parent;
  //              if (Application.Cards.TryGetValue(iteration.Parent, out parent))
   //             {
   //                 ((ProjectDisplayObject)parent).ProjectData.RemoveIteration(iteration);
    //            }
                IterationDisplayObject iter = (IterationDisplayObject)card;

                foreach (StoryCard sc in iter.IterationData.StoryCardChildren)
                {
                    
                    if (Application.Cards.TryGetValue(sc.ID, out card))
                    {
                        card.FadeOut();
                        Application.Cards.Remove(card.ID);
                        
                    }
                }
                iter.IterationData.StoryCardChildren.Clear();

                iter.FadeOut();
            }
        }

        public void OnCreatedBacklog(Backlog backlog) { }

        public void OnCreatedProject(Project project) { }

        public void OnDeletedCard(long id) { }

        public void OnUndeletedIteration(Iteration iteration)
        {
            IterationDisplayObject iter = Application.CreateIteration(iteration);

            foreach (StoryCard sc in iteration.StoryCardChildren)
            {
                iter.AddStoryCard(Application.CreateStoryCard(sc));
            }

            CollectStoryCards(iteration);
        }

        public void OnUndeletedStoryCard(StoryCard storycard)
        {
            StoryCardDisplayObject sc = Application.CreateStoryCard(storycard);

            if (Application.Project == null)
            {
                MessageBox.Show("Cannot undelete story card. Not connected to a project!", "Error");
            }
            else if (storycard.Parent != Application.Project.Backlog.ID)
            {
                DisplayObject parent;

                if (Application.Cards.TryGetValue(storycard.Parent, out parent))
                {
                    ((IterationDisplayObject)parent).AddStoryCard(sc);
                    if (!((IterationDisplayObject)parent).IterationData.StoryCardChildren.Contains(sc.StoryCardData))
                        (((IterationDisplayObject)parent).IterationData).AddStoryCard(sc.StoryCardData);
                }
            }
        }

        public void OnMovedStoryCardToNewParent(StoryCard storycard)
        {
            IterationDisplayObject newparent, oldparent;
            StoryCardDisplayObject card;
            DisplayObject dobj1, dobj2;
            StoryCard before, after;

            if (Application.Cards.TryGetValue(storycard.ID, out dobj1))
            {
                card = (StoryCardDisplayObject)dobj1;

                before = (StoryCard)card.StoryCardData.Clone();

                
                //if it is distributed mode, the old parent should remove child, and new parent should add new child
                //remove from old parent (if it was an iteration)
   //             if (this.app.Persister is AsyncXMLSocketPersister)
                {
                    if (Application.Cards.TryGetValue(card.StoryCardData.Parent, out dobj2))
                    {
                        oldparent = (IterationDisplayObject)dobj2;
                        oldparent.RemoveStoryCard(card);
                        oldparent.IterationData.RemoveStoryCard(card.StoryCardData);
                    }

                    //update the story data
                    card.StoryCardData.Update(storycard);
                    //this is a MoveToNewParent so this value has definitely changed
                    card.StoryCardData.Parent = storycard.Parent;

                    //add to new parent (if it is an iteration)
                    if (Application.Cards.TryGetValue(card.StoryCardData.Parent, out dobj2))
                    {
                        newparent = (IterationDisplayObject)dobj2;
                        newparent.AddStoryCard(card);
                        newparent.IterationData.AddStoryCard(card.StoryCardData);
                    }
                }
                //when persister is local persister, the card object has been updated while writing to xml file, the card has been removed from parent, so, the only thing is to add story card to new parent
    /*            else
                {
                    if (Application.Cards.TryGetValue(card.StoryCardData.Parent, out dobj2))
                    {
                        newparent = (IterationDisplayObject)dobj2;
                        newparent.AddStoryCard(card);
                    }
                }*/

                //if that move was triggered by another client and the local
                //display object is not in the right place -> move it!
                //make sure that the drop event does not trigger a second call
                //to persister by doing the same check that is done here
                //in the DropEventHandler
                //don't use the double values for comparison, they are almost always a bit off
                if (!((int)card.LocationX == (int)(storycard.LocationX*Properties.Settings.Default.Scale) &&
                    (int)card.LocationY == (int)(storycard.LocationY *Properties.Settings.Default.Scale)&&
                    (int)card.RotationAngle == (int)(storycard.RotationAngle*Properties.Settings.Default.Scale)))
                {
                    //do not trigger update event while finishing the anmiation
                    ((StoryCardDisplayObjectImpl)card).setTriggerDropEventAfterAnimiationComplete(false); 
                    card.AnimatedMoveTo(storycard.LocationX*Properties.Settings.Default.Scale, storycard.LocationY*Properties.Settings.Default.Scale, storycard.RotationAngle);
                }

                after = (StoryCard)card.StoryCardData.Clone();
      //          Application.Commands.Add(new StoryCardMoveToNewParentCommand(Application, before, after));
            }

            //iterate through all iterations and update the remaing effort
            foreach (DisplayObject card2 in Application.Cards.Values)
            {
                if (card2 is IterationDisplayObject)
                {
                    ((IterationDisplayObject)card2).UpdateRemainingEffort();
                }
            }
        }

        public void OnUpdatedBacklog(Backlog backlog) { }

        public void OnUpdatedIteration(Iteration iteration)
        {
            DisplayObject dobj;

            if (Application.Cards.TryGetValue(iteration.ID, out dobj))
            {
                var iter = (IterationDisplayObject)dobj;
                //size changed
              if (iteration.Width != iter.IterationData.Width || iteration.Height != iter.IterationData.Height)
                {
                    Iteration before = (Iteration)iter.IterationData.Clone();
                    Iteration after = (Iteration)iter.IterationData.Clone();
                    after.Width = iteration.Width;
                    after.Height = iteration.Height;
 //                   Application.Commands.Add(new IterationUpdateCommand(Application, before, after));
               //     iter.Resize(iteration.Width-iter.IterationData.Width, iteration.Height-iter.IterationData.Height);
                    double dx = (iteration.Width - iter.Width);
                    double dy = (iteration.Height - iter.Height);
                    iter.Resize(ref dx, ref dy );
                    ((DisplayObjectImpl)iter).refitCardCenterAndAbsolutePosition(dx, dy);
                    iter.IterationData.Height = after.Height;
                    iter.IterationData.Width = after.Width;
              
                }
                  //position changed
              else if ( (int)iteration.LocationX     != (int)iter.IterationData.LocationX ||
                        (int)iteration.LocationY     != (int)iter.IterationData.LocationY ||
                        (int)iteration.RotationAngle != (int)iter.IterationData.RotationAngle)
              {
                  //the storycards need to be moved as well!
                  //iter.AnimatedMoveTo(iteration.LocationX*Properties.Settings.Default.Scale, iteration.LocationY*Properties.Settings.Default.Scale, iteration.RotationAngle);

                  Iteration before = (Iteration) iter.IterationData.Clone();

                  double offsetX = iteration.LocationX*Properties.Settings.Default.Scale - iter.LocationX;
                  double offsetY = iteration.LocationY*Properties.Settings.Default.Scale - iter.LocationY;
                  double offAngle = iteration.RotationAngle - iter.RotationAngle;

                  //if the update is called by the APDT iteration movement, the move function should not be called
                  //if it is called by other client, it should be called here
                  if ((int) (iter.LocationX) != (int) (iteration.LocationX*Properties.Settings.Default.Scale) &&
                      (int) (iter.LocationY) != (int) (iteration.LocationY*Properties.Settings.Default.Scale))
                  {
                      iter.AnimatedMoveTo(offsetX, offsetY, offAngle);
                      foreach (StoryCard sc in iter.IterationData.StoryCardChildren)
                      {
                          foreach (StoryCard current in iteration.StoryCardChildren)
                          {
                              if (sc.ID == current.ID) //storycard will notify a display movement
                              {
                                  sc.LocationX = current.LocationX;
                                  sc.LocationY = current.LocationY;
                                  sc.RotationAngle = current.RotationAngle;
                                  break;
                              }
                          }

                      }
                  }
              }
              //update the iteration data object properties
              iter.IterationData.UpdateWithoutNotifyChanges(iteration);
              //refresh all text boxes in the iteration display object ("avaliable effort", "remaining effort", "start date", etc)
              ((IterationDisplayObjectImpl)iter).RefreshTextBoxes();
            }
        }

        public void OnGotProjectNames(List<string> str) { }

        public void OnArrangedProject(Project project) { }

        public void OnGotProjectNamesForLoginEvent(List<string> list) { }

        public void OnDeletedBacklog(Backlog backlog) { }

        public void OnDeletedProject(bool deleted) { }

        public void OnUndeletedBacklog(Backlog backlog) { }

        #endregion

        public void OnMouseMove(MouseMove move)
        {
            if (Application.isRemoteMouseExisted(move))
            {
                Application.UpdateRemoteMouse(move);
            }
            else
            {
                Application.CreateRemoteMouse(move);
            }
            if (move.ThirdPartyObject!=null)
               Application.HandleMouseMoveLoadedEvent(move);
        }

        public void OnOtherClientDisconnected(int clientID)
        {
            Application.DeleteRemoteMouse(clientID);
        }
      

        #region Helper Methods

        protected void CollectStoryCards(Iteration iteration)
        {
            List<StoryCardDisplayObject> collected = new List<StoryCardDisplayObject>();

            foreach (DisplayObject card in Application.Cards.Values)
            {
                if (card is StoryCardDisplayObject)
                {
                    StoryCardDisplayObject sc = (StoryCardDisplayObject)card;
                    //checks to make sure that cards are in the backlog - prevents newly created 
                    //iterations from grabbing other iterations' storycards!
                    if ((sc.ParentID == Project.Backlog.ID) && Util.IsPointInsidePolygon(sc.CenterAbsolute, new Point[] {
                            new Point(iteration.LocationX * Properties.Settings.Default.Scale, iteration.LocationY * Properties.Settings.Default.Scale), 
                            new Point(iteration.LocationX * Properties.Settings.Default.Scale, (iteration.LocationY + iteration.Height) * Properties.Settings.Default.Scale),
                            new Point((iteration.LocationX + iteration.Width) * Properties.Settings.Default.Scale, (iteration.LocationY + iteration.Height) * Properties.Settings.Default.Scale),
                            new Point((iteration.LocationX + iteration.Width) * Properties.Settings.Default.Scale, iteration.LocationY * Properties.Settings.Default.Scale)
                    }))
                    {
                        collected.Add(sc);
                    }
                }
            }

            if (collected.Count > 0)
            {
                double sumRotation = 0;

                foreach (StoryCardDisplayObject sc in collected)
                {
                    sumRotation += (sc.RotationAngle > 0 ? sc.RotationAngle : (sc.RotationAngle + 360));
                }

                //calculate the offsets between the Application.Cards, leave some extra
                //space at the borders
                double offsetX = iteration.Width / (collected.Count + 3);
                double offsetY = iteration.Height / (collected.Count + 3);

                //calculate an average rotation angle for all collected Application.Cards
                double averageRotation = ((sumRotation / collected.Count) + 180) % 360;

                int i = 1;

                foreach (StoryCardDisplayObject sc in collected)
                {
                    Application.BringToFront(sc);
                    //need to think about a solution for undelete iteration
                    //cards are not animated on undelete so they are not added to iteration
                    //sc.AnimatedMoveTo(iteration.LocationX + offsetX * i,
                    //    iteration.LocationY + offsetY * i,
                    //    /*averageRotation*/ iteration.RotationAngle + 3);
                    CheckDrop(sc, (iteration.LocationX + offsetX * i) * Properties.Settings.Default.Scale,
                        (iteration.LocationY + i * offsetY)*Properties.Settings.Default.Scale, iteration.RotationAngle + 3);
                    i++;
                }
            }
        }

        private void CheckDrop(StoryCardDisplayObject sc, double locX, double locY, double rotAngle)
        {
            //the card already is in the right position
            if ((locX == sc.LocationX) && (locY == sc.LocationY) && (rotAngle == sc.RotationAngle))
            {
                sc.IndexCardDropped(sc, new IndexCardDropEventArgs(sc.ID, sc.ParentID, sc.CenterAbsolute, sc.RotationAngle, sc.StoryCardData.Clone(), sc.StoryCardData.Clone()));
            }
            else
            {
                sc.AnimatedMoveTo(locX, locY, rotAngle);
            }

        }

        #endregion

        #region PlannerDataChangeListener Members


        public void OnLiveUpdated(LiveUpdate update)
        {
              DisplayObject dobj;

              if (Application.Cards.TryGetValue(update.ID, out dobj))
              {
                  if (dobj is StoryCardDisplayObject)
                  {
                      ((StoryCardDisplayObjectImpl)dobj).UpdateTextBox(update.Text, update.Field);
                  }
              }
            
        }

        public void OnCardMinimized(long ID,bool isMinimized)
        {
            DisplayObject dobj;

            if (Application.Cards.TryGetValue(ID, out dobj))
            {
                if (dobj is StoryCardDisplayObject)
                
                    ((StoryCardDisplayObjectImpl)dobj).CardMinimized(isMinimized);
            }

        }


        #endregion

        #region Dot Voting

        public void OnStartedVoting()
        {
            Application.IsVotingEnabled = true;
            Console.WriteLine("PersisterModuleImpl] enabled voting");
        }

        public void OnStoppedVoting()
        {
            Application.IsVotingEnabled = false;
            Console.WriteLine("PersisterModuleImpl] disabled voting");
        }

        public void OnVotedCard(VoteDot dot)
        {
            DisplayObject dobj;

            if (Application.Cards.TryGetValue(dot.CardID, out dobj))
            {
                if (dobj is StoryCardDisplayObject)
                {
                    ((StoryCardDisplayObjectImpl)dobj).drawVoteDot(dot.X, dot.Y);
                }
            }
        }

        public void OnClearVotes()
        {
            Application.ClearVoteDots();
        }

        #endregion



        public void OnUpdatedProject(Project project)
        {
            OnLoadedProject(project);
        }
    }
}
